﻿
Imports System.IO
Imports std

Public Module monitor

    Public Const defaultConfigfile As String = "monitor.ini"
    Friend WithEvents config As configMonitor = Nothing
    Friend cases As list(Of caseType) = Nothing
    Friend sleeptime As UInt64 = configNaming.defaultValue.monitor.sleeptime

    Friend Class caseType
        Public name As String
        Public [case] As String
        Public argument As String
        Public expectedReturn As Int64
        Public failureCount As Int64
        Public failureUpperBound As Int64
        Public falseAction As String
        Public falseArgument As String
    End Class

    Friend Sub initial()
        assert(Not config Is Nothing, "config is nothing when initial.")
        If cases Is Nothing Then
            cases = New list(Of caseType)
        Else
            cases.clear()
        End If
        Dim str As String = Nothing
        str = config.parameter(Of String)(configNaming.monitor.name, configNaming.monitor.cases)
        Dim caselist As vector(Of String) = Nothing
        strsepar(str, character.comma, caselist)
        Dim i As Int64
        For i = 0 To caselist.size() - 1
            Dim [case] As caseType = Nothing
            cases.push_back(New caseType())
            [case] = cases.last()
            [case].name = caselist(i)
            [case].case = config.parameter(Of String)(caselist(i), configNaming.case.case)
            [case].argument = config.parameter(caselist(i), configNaming.case.argument, _
                                               configNaming.defaultValue.case.argument)
            [case].expectedReturn = config.parameter(Of Int64)(caselist(i), configNaming.case.expectedReturn _
                                                               , configNaming.defaultValue.case.expectedReturn)
            [case].failureUpperBound = config.parameter(caselist(i), configNaming.case.failureUpperBound, _
                                                        configNaming.defaultValue.case.failureUpperBound)
            [case].falseAction = config.parameter(Of String)(caselist(i), configNaming.case.falseAction)
            [case].falseArgument = config.parameter(Of String)(caselist(i), configNaming.case.falseArgument _
                                                               , configNaming.defaultValue.case.falseArgument)
            raiseError("case[" + Convert.ToString(i) + "] " + [case].name + " app " + [case].case + _
                       " argument " + [case].argument + _
                       " failure_upperbound " + Convert.ToString([case].failureUpperBound) + _
                       " false_action " + [case].falseAction + _
                       " false_argument " + [case].falseArgument, errorHandle.errorType.application)
        Next
        sleeptime = config.parameter(Of UInt64)(configNaming.monitor.name, configNaming.monitor.sleeptime _
                                                   , configNaming.defaultValue.monitor.sleeptime)
    End Sub

    Private Function caseWorkingDir(ByVal c As caseType) As String
        assert(Not c Is Nothing)
        Return ifoper(Directory.Exists(c.name), _
                      getfullpath(c.name), _
                      getfullpath(filesystem.thislevelPathMark))
    End Function

    Private Function beforestart(ByVal p As Process, ByVal c As caseType, ByVal app As String) As Boolean
        assert(Not p Is Nothing)
        assert(Not c Is Nothing)
        p.StartInfo.WorkingDirectory = caseWorkingDir(c)
        p.StartInfo.UseShellExecute = False
        p.StartInfo.RedirectStandardError = True
        p.StartInfo.RedirectStandardOutput = True
        Return True
    End Function

    Private Function startprocessEx(ByVal c As caseType, ByVal app As String, ByVal argument As String, _
                                    ByRef o As String, ByRef e As String, ByRef ec As Int32) As Boolean
        assert(Not c Is Nothing)
        Dim p As Process = Nothing
        If startprocess(p, app, argument, Function(ByRef proc As Process) As Boolean
                                              Return beforestart(proc, c, app)
                                          End Function) Then
            p.WaitForExit()
            ec = processExitCode(p)
            o = p.StandardOutput.ReadToEnd()
            e = p.StandardError.ReadToEnd()
            closeProcess(p)
            Return True
        Else
            Return False
        End If
    End Function

    Private Function caseaction(ByVal c As caseType, _
                                ByRef o As String, ByRef e As String, ByRef ec As Int32) As Boolean
        assert(Not c Is Nothing)
        assert(Not isemptystring(c.case))
        Return startprocessEx(c, c.case, c.argument, o, e, ec)
    End Function

    Private Function falseaction(ByVal c As caseType, _
                                 ByRef o As String, ByRef e As String, ByRef ec As Int32) As Boolean
        assert(Not c Is Nothing)
        assert(Not isemptystring(c.falseAction))
        Return startprocessEx(c, c.falseAction, c.falseArgument, o, e, ec)
    End Function

    Private Sub startService()
        On Error Resume Next

        If config.parameter(configNaming.monitor.name, _
                            configNaming.monitor.waitbeforemonitor, _
                            configNaming.defaultValue.monitor.waitbeforemonitor) Then
            sleepSeconds(60 * 60)
        End If
        Dim lastrun As Int64 = ticksToMilliseconds(Now.Ticks()) - sleeptime
        While Not consoleExtSignal()
            If Not config.configRefreshing() AndAlso ticksToMilliseconds(Now.Ticks()) - lastrun >= sleeptime Then
                lastrun = ticksToMilliseconds(Now.Ticks())
                Dim i As Int64
                Dim [case] As caseType = Nothing
                For i = 0 To cases.size() - 1
                    [case] = cases(i)
                    If Not [case] Is Nothing AndAlso Not isemptystring([case].case) Then
                        Dim out As String = Nothing
                        Dim err As String = Nothing
                        Dim extCode As Int32 = 0
                        If caseaction([case], out, err, extCode) Then
                            raiseError("case action " + [case].name + " output " + out + ", error " + err, _
                                       errorHandle.errorType.application)
                            If extCode <> [case].expectedReturn Then
                                [case].failureCount += 1
                                raiseError("caught case " + [case].name + " returns " + Convert.ToString(extCode) + _
                                           " do not match with expected return " + _
                                           Convert.ToString([case].expectedReturn) + _
                                           ". increase failure count to " + Convert.ToString([case].failureCount), _
                                           errorHandle.errorType.application)
                                If [case].failureCount = [case].failureUpperBound Then
                                    [case].failureCount = 0
                                    raiseError("case " + [case].name + " failure count reached upper bound, " + _
                                               "start false action.", errorHandle.errorType.application)
                                    If Not isemptystring([case].falseAction) Then
                                        If falseaction([case], out, err, extCode) Then
                                            raiseError("false action ", [case].name, " output ", out, ", error ", err, _
                                                       errorHandle.errorType.application)
                                        Else
                                            raiseError("cannot start false action for case " + [case].name + _
                                                       ", app " + [case].falseAction + _
                                                       " with argument " + [case].falseArgument + ", ignore", _
                                                       errorHandle.errorType.exclamation)
                                        End If
                                    Else
                                        raiseError("do not get valid false action of " + [case].name + ", ignore.", _
                                                   errorHandle.errorType.warning)
                                    End If
                                End If
                            Else
                                [case].failureCount = 0
                            End If
                        Else
                            raiseError("cannot start case " + [case].name + ", app " + [case].case + _
                                       " with argument " + [case].argument + ", ignore", _
                                       errorHandle.errorType.exclamation)
                        End If
                    Else
                        Dim casename As String = Nothing
                        If [case] Is Nothing Then
                            casename = "##UNKNOWN##"
                        Else
                            casename = [case].name
                        End If
                        raiseError("do not get valid case action of " + casename + ", ignore.", _
                                   errorHandle.errorType.warning)
                    End If
                Next
            Else
                sleep()
            End If
        End While

        raiseError("caught console cancel key event, shutdown...")
    End Sub

    Public Sub shutdown()
        errorHandle.finalize()
    End Sub

    Public Sub unhandledException(ByVal ex As Exception)
        shutdown()
    End Sub

    Public unhandledExceptionHandler As UnhandledExceptionDelegate = AddressOf unhandledException

    Public Sub main(ByVal args() As String)
        errorHandle.initial(defaultLogFilename())
        registerUnhandledException(unhandledExceptionHandler)
        destructor.start()
        registerConsoleExtHandle()

        Dim configfile As String = defaultConfigfile
        If args.Length > 0 Then
            configfile = args(0)
            raiseError("use argument0 " + configfile + "as config file, instead of default config file")
        End If

        config = New configMonitor()
        If config.open(configfile) Then
            initial()
            raiseError("load configuration success, start service.")
            startService()
        Else
            raiseError("cannot load config file " + configfile, errorHandle.errorType.exclamation)
        End If
        destructor.stop()
        shutdown()
        consoleExtHandleFinish()
    End Sub

    Private Sub config_ConfigChanged() Handles config.configChanged
        initial()
    End Sub
End Module
